package db.swing2;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import de.dnb.basics.Misc;
import de.dnb.basics.applicationComponents.LFUCache;
import de.dnb.basics.applicationComponents.LFUCachedFunction;
import de.dnb.basics.applicationComponents.LRUCachedFunction;
import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.filtering.IFunction;
import de.dnb.gnd.parser.Record;
import de.dnb.gnd.utils.RecordUtils;

public class Controller {

	private static final int OBUB_CACHE_SIZE = 100000;

	private View view;

	private DBConnection dbConnection;

	/**
	 * Setzt zu einem angeklickten Begriff in der Baumdarstellung den
	 * zugehörigen Datensatz in das rechte Fenster (über Portal). Funktioniert
	 * auch bei Cursor.
	 * 
	 * @author baumann
	 * 
	 */
	public class OBUBTreeSelectionListener implements TreeSelectionListener {

		@Override
		public void valueChanged(final TreeSelectionEvent event) {
			final TreePath treePath = event.getPath();
			final Object pathComponent = treePath.getLastPathComponent();
			if (!(pathComponent instanceof OBUBTreeNode))
				return;
			OBUBTreeNode treeNode = (OBUBTreeNode) pathComponent;
			final String idn = treeNode.idn;
			getRecordAndPutInTextArea(idn);
		}
	}

	/**
	 * Bei Doppelklick auf Baumelement wird dieses in die Mitte gerückt.
	 * 
	 * @author Christian_2
	 * 
	 */
	public class OBUBTreeDoubleClickListener extends MouseAdapter {
		public OBUBTreeDoubleClickListener(JTree tree) {
			super();
			this.tree = tree;
		}

		private final JTree tree;

		@Override
		public void mousePressed(MouseEvent e) {
			int selRow = tree.getRowForLocation(e.getX(), e.getY());
			TreePath treePath = tree.getPathForLocation(e.getX(), e.getY());
			if (selRow != -1) {
				final Object pathComponent = treePath.getLastPathComponent();
				if (!(pathComponent instanceof OBUBTreeNode))
					return;
				OBUBTreeNode treeNode = (OBUBTreeNode) pathComponent;
				final String idn = treeNode.idn;

				if (e.getClickCount() == 2) {
					String name = treeNode.toString();
					loadAndSetTrees(idn, name);
				}
			}
		}
	}

	private LFUCachedFunction<String, Record> getRecord =
			new LFUCachedFunction<String, Record>() {
				@Override
				protected Record calculate(String idn) {
					return RecordUtils.readFromPortal(idn);
				}
			};

	/**
	 * @param idn
	 */
	protected void getRecordAndPutInTextArea(final String idn) {

		Thread thread = new Thread() {
			public void run() {
				Record record = getRecord.apply(idn);
				view.setRecord(record);

			};
		};
		thread.start();
	}

	public class MyWindowClosingListener extends WindowAdapter {
		@Override
		public void windowClosing(final WindowEvent event) {
			dbConnection.close();
			view.close();
		}
	}

	public class MyHitListSelectionListener implements ListSelectionListener {
		@Override
		public void valueChanged(final ListSelectionEvent e) {
			// sonst werden für jeden Klick 2 Events erzeugt:
			if (e.getValueIsAdjusting()) {
				return;
			}
			final int selectedRow = view.getSelectedHitListRow();
			if (selectedRow >= 0) {

				HitListTableModel model = view.getHitListTableModel();
				Pair<String, String> pair =
						model.getIDandNAmeForSelectedRow(selectedRow);
				if (pair != null) {
					final String idn = pair.first;
					final String name = pair.second;
					loadAndSetTrees(idn, name);
				}
			}
		}
	}

	public class ExpandTreeListener implements ActionListener {

		boolean expand = false;

		public ExpandTreeListener(JTree tree) {
			super();
			this.tree = tree;
		}

		final JTree tree;

		@Override
		public void actionPerformed(ActionEvent arg0) {
			expand = !expand;
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					view.setBusyCursor(true);
					Misc.expandAll(tree, expand);
					view.setBusyCursor(false);
				}
			});

		}
	}

	public class SearchIDActionListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			String id = view.getSearchIDN();
			ResultSet resultSet = dbConnection.getResultsetForID(id);
			HitListTableModel model;
			try {
				model = new HitListTableModel(resultSet);
			} catch (SQLException e1) {
				model = null;
			}
			String text = "idn=" + id;
			view.setSearchTerm(text);
			view.setHitListModel(model);
		}

	}

	public class SearchNameActionListener implements ActionListener,
			KeyListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			doAction();
		}

		protected void doAction() {
			final String name = view.getSearchTerm();
			view.setStatus("suche \"" + name + "\"");
			view.setBusyCursor(true);
			Thread thread = new Thread() {
				@Override
				public void run() {
					String type = view.getSelectedRadioButton();
					HitListTableModel model =
							cachedHitlistFunction
									.apply(new Pair<String, String>(name, type));
					view.setHitListModel(model);
					String text = "\"" + name;
					if (type.length() == 1)
						text += " and bbg T" + type;
					text += "\"";
					view.setSearchTerm(text);
					view.setStatus("");
					view.setBusyCursor(false);
				}
			};
			thread.start();

		}

		@Override
		public void keyPressed(KeyEvent e) {
		}

		@Override
		public void keyReleased(KeyEvent e) {
			doAction();
		}

		@Override
		public void keyTyped(KeyEvent e) {
		}
	}

	// @formatter:on
	IFunction<Pair<String, String>, HitListTableModel> cachedHitlistFunction =
			new LRUCachedFunction<Pair<String, String>, HitListTableModel>() {
				@Override
				protected HitListTableModel calculate(Pair<String, String> p) {
					HitListTableModel model;
					ResultSet resultSet;
					String name = p.first;
					String type = p.second;
					if (type.length() > 1) {
						resultSet = dbConnection.getResultSetForName(name);
					} else {
						resultSet =
								dbConnection.getResultSetForNameAndType(name,
										type.charAt(0));
					}
					try {
						model = new HitListTableModel(resultSet);
					} catch (SQLException e) {
						model = null;
					}
					return model;
				}

			};

	private final class MyHyperlinkListener implements HyperlinkListener {
		public void hyperlinkUpdate(HyperlinkEvent e) {
			if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
				URL url = e.getURL();
				if (url == null)
					return;
				String id = url.getAuthority();
				if (id == null)
					return;
				ResultSet resultSet = dbConnection.getResultsetForID(id);
				HitListTableModel model;
				try {
					model = new HitListTableModel(resultSet);
				} catch (SQLException e1) {
					model = null;
				}
				view.setHitListModel(model);
				view.setSearchTerm("idn = " + id);
				getRecordAndPutInTextArea(id);
			}
		}
	}

	private synchronized void loadAndSetTrees(final String idn,
			final String name) {
		view.setStatus("Lade Baumdarstellung für \"" + name + "\"");
		view.setBusyCursor(true);

		Thread thread = new Thread() {
			@Override
			public void run() {
				OBUBTreeNode ubNode =
						createTreeForIDAndName(idn, name, cachedUBBFunction);
				view.setUBTree(ubNode);
				OBUBTreeNode obNode =
						createTreeForIDAndName(idn, name, cachedOBBFunction);
				view.setOBTree(obNode);
				getRecordAndPutInTextArea(idn);
				view.setStatus("");
				view.setBusyCursor(false);
			}
		};
		thread.start();
	}

	private IFunction<String, List<Pair<String, String>>> cachedUBBFunction =
			new LRUCachedFunction<String, List<Pair<String, String>>>(
					OBUB_CACHE_SIZE) {
				@Override
				protected List<Pair<String, String>> calculate(String x) {
					return dbConnection.getUBB(x);
				}
			};

	private IFunction<String, List<Pair<String, String>>> cachedOBBFunction =
			new LRUCachedFunction<String, List<Pair<String, String>>>(
					OBUB_CACHE_SIZE) {
				@Override
				protected List<Pair<String, String>> calculate(String x) {
					return dbConnection.getOBB(x);
				}
			};

	//@formatter:off
	/**
	 * Enthält die gecachten Ober- und Unterbegriffsbäume. Diese werden
	 * der Einfachheit halber mit den Funktionen indiziert, die die
	 * Suche nach Kindern oder Eltern vornehmen.
	 */
	private HashMap<IFunction<String, List<Pair<String, String>>>, 
		LFUCache<String, OBUBTreeNode>> treeCaches =
			new HashMap<IFunction<String, List<Pair<String, String>>>, 
				LFUCache<String, OBUBTreeNode>>();
	// @formatter:on

	/**
	 * Gibt einer idn die Baumstruktur der Unter- und Oberbegriffe.
	 * 
	 * @param id
	 *            beliebig
	 * @param name
	 *            beliebig
	 * @param treeFunction
	 *            cachedUBB/OBBFunction
	 * @return einen Baum der UBB oder OBB oder einen leeren Knoten
	 */
	public final OBUBTreeNode createTreeForIDAndName(final String id,
			final String name,
			final IFunction<String, List<Pair<String, String>>> treeFunction) {

		if (name == null || name.isEmpty() || id == null || id.isEmpty())
			return null;
		LFUCache<String, OBUBTreeNode> cache = treeCaches.get(treeFunction);
		OBUBTreeNode node = cache.get(id);
		if (node != null)
			return node;
		// Pfad der von der Wurzel bis zum aktuellen Knoten reicht (idn-Liste)
		LinkedHashSet<String> path = new LinkedHashSet<String>();
		path.add(id);
		node = makeSubTree(id, name, treeFunction, new LinkedHashSet<String>());
		cache.put(id, node);
		return node;
	}

	private OBUBTreeNode makeSubTree(final String id, final String name,
			IFunction<String, List<Pair<String, String>>> treeFunction,
			LinkedHashSet<String> path) {
		final OBUBTreeNode node = new OBUBTreeNode(id, name);
		List<Pair<String, String>> pairs = treeFunction.apply(id);
		int index = 0;
		for (Pair<String, String> pair : pairs) {
			String ubID = pair.first;
			String ubName = pair.second;
			if (ubName == null || ubName.isEmpty()) // Abbruch
				continue;
			if (path.contains(ubID)) {
				reportCycle(ubID, path);
			} else {
				path.add(ubID);
				node.insert(
						makeSubTree(pair.first, pair.second, treeFunction, path),
						index);
				index++;
				path.remove(ubID);
			}
		}
		return node;
	}

	/**
	 * @param ubID
	 *            id des Datensatzes, bei dem der Zyklus endet
	 * @param path
	 */
	protected void reportCycle(final String ubID, LinkedHashSet<String> path) {
		String s = "Zyklus erkannt: ";
		boolean print = false;
		for (String idPath : path) {
			if (idPath.equals(ubID))
				print = true;
			if (print)
				s +=
						idPath + " (" + dbConnection.getNameForIDN(idPath)
								+ ") - ";
		}
		s += ubID + " (" + dbConnection.getNameForIDN(ubID) + ")";
		view.addError(s);
	}

	public Controller() {
		view = new View();
		treeCaches.put(cachedOBBFunction, new LFUCache<String, OBUBTreeNode>(
				1000, 0.8f));
		treeCaches.put(cachedUBBFunction, new LFUCache<String, OBUBTreeNode>(
				1000, 0.8f));
		try {
			dbConnection = new DBConnection("./dbconfig/db.properties");
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		view.setStatus(""); // Zu Beginn: Initialisiere ...
		view.setBusyCursor(false);
		view.addWindowClosingListener(new MyWindowClosingListener());
		view.setHitListSelectionListener(new MyHitListSelectionListener());
		view.setSearchIDActionListener(new SearchIDActionListener());
		view.setSearchNameActionListener(new SearchNameActionListener());
		view.setLinkListener(new MyHyperlinkListener());
		view.setOBExpandListener(new ExpandTreeListener(view.getOBBTree()));
		view.setUBExpandListener(new ExpandTreeListener(view.getUBBTree()));
		// für Veränderung der Baumdarstellungen:
		view.setUBBTreeMouseListener(new OBUBTreeDoubleClickListener(view
				.getUBBTree()));
		view.setOBBTreeMouseListener(new OBUBTreeDoubleClickListener(view
				.getOBBTree()));
		// Holt Datensatz aus Baumdarstellung heraus:
		view.setTreeSelectionListener(new OBUBTreeSelectionListener());
	}

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		new Controller();
	}

}
